#include "scene.h"
#include "Scripting/scripting.h"

#include <cmath>
#include "Mathematics/Matrices/matrix4x4.h"

#include <ctime>
#include <iostream>
#include <SDL/SDL_image.h>
#include <SDL/SDL_ttf.h>
using namespace std;

#undef main

CRenderer Renderer;
CScene Scene;
CWindow Window;

SDL_Event Event;

bool bEnd = false;

struct _FPSCounter
{
    int nFPS;
    int nFrames;
    float fLastCountTime;

    void Init();
    void Update();
} FPSCounter;

struct _Ball
{
    float x, y, z;
    float fSpeedX, fSpeedZ;
    float r;

    void Init();
    void Update();
} Ball;

struct _Rocket
{
    const static float y = 0.0f, z = 23.0f, w = 5.0f, d = 0.5f;

    float x;

    void Init();
    void Update();
} Rocket;

struct _Walls
{
    float x, x2, w;
    float z, d;

    void Init();
} Walls;

struct _Block
{
    float x, z;
    float w, d;

    bool bActive;

    void Init(float x, float z, float w, float d);

    void Destroy(int nNo);
} *Blocks;
int nBlocksNo = 0;

void InitBlocks();

void Collisions();

int nPoints = 0;
TTF_Font *Font;

int main()
{
	cout << "Started.\n";

	srand(time(NULL));

	Renderer.Init();
	Renderer.LinkWindow(&Window);
	Renderer.SetDisplayMode(800, 600, 32, SDL_RESIZABLE | SDL_OPENGL);

    Scene.SetRenderer(&Renderer);

    TTF_Init();
    Font = TTF_OpenFont("data/font.ttf", 26);

    //---

    ScrVariables.AutoInit();

    ScrLoadScript("data/default.scn");
    ScrDoScript();

    for(Uint32 i = 0; i < ScrVariables.RenderObjectNames.size(); ++i)
    {
        Renderer.AddObject((*ScrVariables.pRenderObjects)[ScrVariables.RenderObjectNames[i]]);
    }

    ShaderProgram *pSPECIALSHADER = (*ScrVariables.pShaders)["NORMALMAPPING_SHADER"],
        *pLIGHTSHADER = (*ScrVariables.pShaders)["LIGHT_SHADER"];

    float fvLightPos[3] = {0.0f, 30.0f, 50.0f};
    pSPECIALSHADER->AddVector3("inLightPos", fvLightPos);
    pLIGHTSHADER->AddVector3("inLightPos", fvLightPos);

    Matrix4x4 *matBall = ((*ScrVariables.pRenderObjects)["BALL_ROBJ"])->GetWorldMatrixPointer();
    Matrix4x4 *matRocket = ((*ScrVariables.pRenderObjects)["ROCKET_ROBJ"])->GetWorldMatrixPointer();

    //---

    Matrix4x4 &matView = *Renderer.GetMatViewPointer();
    matView.Identity();
    matView.Look(0, 25, 70, 0, 0, 1);

    FPSCounter.Init();

    Ball.Init();
    Rocket.Init();
    Walls.Init();

    InitBlocks();

	while(!bEnd)
	{
		while(SDL_PollEvent(&Event))
		{
			switch(Event.type)
			{
				case SDL_QUIT:
					bEnd = true;
					break;
				case SDL_KEYDOWN:
					if(Event.key.keysym.sym == SDLK_ESCAPE) bEnd = true;
					break;
				case SDL_VIDEORESIZE:
					Renderer.ChangeDisplaySize(Event.resize.w, Event.resize.h);
					break;
			}
		}
		Uint8 *keys = SDL_GetKeyState(NULL);
		if(keys[SDLK_w]) matView.Translate(0, 0, 1);
		if(keys[SDLK_s]) matView.Translate(0, 0, -1);
		if(keys[SDLK_a]) matView.Translate(1, 0, 0);
		if(keys[SDLK_d]) matView.Translate(-1, 0, 0);
		if(keys[SDLK_z]) matView.Translate(0, -1, 0);
		if(keys[SDLK_x]) matView.Translate(0, 1, 0);

        Rocket.Update();
        Ball.Update();

        Collisions();

        matBall->Identity();
        matBall->Translate(Ball.x, Ball.y, Ball.z);

        matRocket->Identity();
        matRocket->Translate(Rocket.x, Rocket.y, Rocket.z);


        Scene.Render();

        //

        FPSCounter.Update();

        char chFPS[6] = {'\0'};
        itoa(FPSCounter.nFPS, chFPS, 10);

        SDL_WM_SetCaption(chFPS, NULL);

		SDL_GL_SwapBuffers();
	}

	TTF_Quit();

	cout << "Exiting.\n";
	return 0;
}

void _FPSCounter::Init()
{
    nFrames = 0;
    fLastCountTime = 0;
}

void _FPSCounter::Update()
{
    if(SDL_GetTicks() - fLastCountTime >= 1000)
    {
        nFPS = nFrames;
        fLastCountTime = SDL_GetTicks();
        nFrames = 0;
    }
    ++nFrames;
}

void _Ball::Init()
{
    x = z = 0.0f;
    y = 0.1f;

    fSpeedX = ((float)(rand()%2000))/10000.0f - 0.1f;
    fSpeedZ = -0.1f;

    r = 1.0f;

    if(abs(fSpeedX) < 0.05f) return this->Init();
}

void _Ball::Update()
{
    x += fSpeedX;
    z += fSpeedZ;
}

void _Rocket::Init()
{
    x = 0.0f;
}

void _Rocket::Update()
{
    Uint8 *keys = SDL_GetKeyState(NULL);

    if(keys[SDLK_LEFT]) x -= 0.1f;
    if(keys[SDLK_RIGHT]) x += 0.1f;
}

void _Walls::Init()
{
    x = -15;
    x2 = 15;
    w = 2;

    z = 7.5f;
    d = 15;
}

void Collisions() //buahaha, this name is so ridiculous...
{
    while (Rocket.x + Rocket.w > Walls.x2) Rocket.x -= 0.1f;
    while (Rocket.x < Walls.x + Walls.w) Rocket.x += 0.1f;

    if (Ball.z - Ball.r < Walls.z - Walls.d) Ball.fSpeedZ *= -1;

    if (Ball.x - Ball.r < Walls.x + Walls.w) Ball.fSpeedX *= -1;
    if (Ball.x + Ball.r > Walls.x2) Ball.fSpeedX *= -1;

    if (Ball.z + Ball.r > Rocket.z - Rocket.d)
    {
        if(Ball.x + Ball.r > Rocket.x && Ball.x - Ball.r < Rocket.x + Rocket.w)
        {
            //float fSpeedSum = Ball.fSpeedX + Ball.fSpeedZ;

            if(Ball.z + Ball.r > Rocket.z - Rocket.d) Ball.z -= Ball.fSpeedZ;

            //Ball.fSpeedX = -((Rocket.x+(Rocket.w/2))-(Ball.x)) * 0.07f;
            //Ball.fSpeedZ += fSpeedSum - Ball.fSpeedX;

            //Ball.fSpeedX *= -1;
            float fRelX = Ball.x - (Rocket.x+(Rocket.w/2));

            Ball.fSpeedZ += 0.01f;
            Ball.fSpeedZ *= -1.0f;

            Ball.fSpeedX = fRelX/27.0f;
        }
        else
        {
            nPoints -= 50;

            SDL_Delay(250);

            Ball.Init();
            Rocket.Init();
        }
    }

    for(int i = 0; i < nBlocksNo; ++i)
    {
        if(Ball.z + Ball.r > Blocks[i].z && Ball.z - Ball.r < Blocks[i].z + Blocks[i].d &&
           Ball.x + Ball.r > Blocks[i].x && Ball.x - Ball.r < Blocks[i].x + Blocks[i].w)
        {
            nPoints += (int)(10.0f*abs(Ball.fSpeedZ));

            Ball.fSpeedZ *= -1;
            Blocks[i].Destroy(i);
            break;
        }
    }
}

void _Block::Init(float xx, float zz, float ww, float dd)
{
    x = xx;
    z = zz;
    w = ww;
    d = dd;

    bActive = true;
}

void InitBlocks()
{
    float w = 4.0f;
    float d = 1.0f;

    float fMaxW = -Walls.x-Walls.w-0.1f;

    vector<Matrix4x4*> mats;
    int n = 0;

    for(int i = 0; i >= 0; ++i)
    {
        string str = "BLOCK";
        str += ((char)(i)+'0');
        str += "_ROBJ";

        if((*ScrVariables.pRenderObjects).find(str) == (*ScrVariables.pRenderObjects).end()) break;

        Matrix4x4 *mat = ((*ScrVariables.pRenderObjects)[str])->GetWorldMatrixPointer();
        if(mat == NULL) break;

        mats.push_back(mat);

        ++n;
    }

    Blocks = new _Block[n];
    nBlocksNo = n;

    float z = -8.0f;
    float x = -fMaxW;
    for(int i = 0; i < n; ++i)
    {
        if(x + w > fMaxW)
        {
            z += d+0.3f;
            x = -fMaxW;
        }

        Blocks[i].Init(x, z, w, d);
        mats[i]->Identity();
        mats[i]->Translate(x, 0, z);

        x += (w+0.12f);
    }
}

void _Block::Destroy(int nNo)
{
    bActive = false;
    x = w = d = 0.0f;
    z = 100.0f;

    string str = "BLOCK";
    str += ((char)(nNo)+'0');
    str += "_ROBJ";

    Matrix4x4* m = ((*ScrVariables.pRenderObjects)[str])->GetWorldMatrixPointer();
    m->Identity();
    m->Translate(x, 0.0f, z);
}
